package com.summer.bilibili.util;

import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import org.springframework.stereotype.Component;

import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.LockSupport;
import java.net.InetAddress;
import java.net.UnknownHostException;


/**
 * 重写MP雪花算法
 * 分布式ID生成器实现
 * 特性：秒级时间戳(10位) + 序列号(4位) + 机器ID(2位)
 */
@Component
public class CustomIdGenerator implements IdentifierGenerator {
    // 序列号最大值（4位十进制数，范围0-9999）
    private static final int MAX_SEQUENCE = 9999;
    private final int machineId;
    // 记录上次生成ID的时间戳（秒级）
    private final AtomicLong lastTimestamp = new AtomicLong ( 0 );
    // 当前序列号（每秒钟重置）
    private final AtomicLong sequence = new AtomicLong ( 0 );

    private volatile long timeCache = 0; // 时间缓存
    private volatile long cacheMillis = 0; // 缓存时间戳

    /**
     * 构造函数-自动生成机器ID
     * 无参，自动计算machineId
     */
    public CustomIdGenerator () {
        // 初始化自动计算及其ID
        this.machineId = computeMachineId ( );
    }

    /**
     * 生成id
     *
     * @param entity
     * @return 生成的唯一id
     */
    @Override
    public synchronized Number nextId ( Object entity ) {
        long currSec = getCurrentSecond ( ); // 获取当前秒级时间戳
        long lastSec = lastTimestamp.get ( );//上次

        // 时钟回拨检测：如果当前时间小于上次记录时间
        if ( currSec < lastSec ) {
            handleClockBackward ( currSec , lastSec );
            // 重新获取
            currSec = getCurrentSecond ( );
        }

        // 序列号管理
        long seq;
        if ( currSec == lastSec ) {
            // 同一秒内，序列号自增
            seq = sequence.incrementAndGet ( );
            // 溢出检查
            if ( seq > MAX_SEQUENCE ) {
                // 等待进入下一秒
                waitNextSecond ( lastSec );
                // 重置
                sequence.set ( 0 );
                seq = 0;
                lastTimestamp.set ( currSec + 1 ); // 使用新时间戳
                currSec = getCurrentSecond ( ); // 重新获取当前时间
            }
        } else {
            // 新的一秒，重置序列号
            sequence.set ( 0 );
            seq = 0;
            lastTimestamp.set ( currSec );
        }
        //返回组合id
        return composeId ( currSec , seq );
    }

    /**
     * ID组合算法
     *
     * @param timestamp
     * @param seq
     * @return 结构：[秒级时间戳(10位)][序列号(4位)][机器ID(2位)]<p>
     * 示例：1672531200(时间) + 0001(序列) + 05(机器) → 1672531200000105
     */
    private long composeId ( long timestamp , long seq ) {
        return timestamp * 1_000_000L + seq * 100L + machineId;
    }

    /**
     * 等待进入下一秒（序列号溢出时调用）
     *
     * @param anchorTime 锚点时间（需要超过这个时间）
     */
    private void waitNextSecond ( long anchorTime ) {
        long current;
        do {
            // 使用LockSupport进行精准休眠（100微秒）
            LockSupport.parkNanos ( 100_000 ); // 等待100微秒

            // 获取新时间前更新缓存
            updateTimeCache ( );
            current = timeCache;

            // 循环直到时间超过锚点
        } while (current == anchorTime);
    }

    /**
     * 获取当前秒级时间（带缓存优化）
     *
     * @return 当前秒级时间戳
     */
    private long getCurrentSecond () {
        long now = System.currentTimeMillis ( );

        // 每10毫秒更新一次缓存
        if ( now - cacheMillis > 10 ) {
            updateTimeCache ( );
        }

        return timeCache;
    }

    /**
     * 更新时间缓存
     */
    private void updateTimeCache () {
        long now = System.currentTimeMillis ( );
        timeCache = now / 1000;
        cacheMillis = now;
    }

    /**
     * 时钟回拨处理策略
     *
     * @param current 当前时间戳
     * @param last    上次记录时间戳
     */
    private void handleClockBackward ( long current , long last ) {
        // 生产环境建议改为日志告警而非直接抛出异常
        System.err.printf ( "WARN: Clock moved backwards! %d < %d%n" , current , last );

        // 策略1：等待时钟追回
        while (current < last) {
            LockSupport.parkNanos ( 1_000_000 ); // 等待1毫秒
            current = getCurrentSecond ( );
        }

        // 策略2：使用最后时间戳+1（根据业务需求选择）
        // lastTimestamp.set(last + 1);
    }

    /**
     * 自动计算分布式机器ID
     *
     * @return 0-99范围内的机器标识符
     */
    private int computeMachineId () {
        try {
            // 获取主机名作为生成基础
            String hostName = InetAddress.getLocalHost ( ).getHostName ( );
            // 计算主机名的哈希值
            int hash = hostName.hashCode ( );
            return Math.abs ( hash ) % 100; // 返回0-99的值
        } catch (UnknownHostException e) {
            // 生成随机ID作为后备方案
            return (int) (System.nanoTime ( ) % 100);
        }
    }
}
